%%%-------------------------------------------------------------------
%%% @doc  
%%% @end
%%%-------------------------------------------------------------------
-module(resource_server).

-behaviour(gen_server).
%%--------------------------------------------------------------------
%% Include files
%%--------------------------------------------------------------------
-include("drproxy_client.hrl").

%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
-export([
	 start_link/0, alloc/0, monitor/1, monitor/2, check/0, check/1,
	 status/0, release/1, restart/1, restart_all/0, restart_all/3,
	 alive/2
	 ]).

%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	terminate/2, code_change/3]).

%%--------------------------------------------------------------------
%% record definitions
%%--------------------------------------------------------------------

-record(state, {resources = [], monitored = [], spares = [],
				alert_pid = false, alert = false}).

%%--------------------------------------------------------------------
%% macro definitions
%%--------------------------------------------------------------------

-define(SERVER, ?MODULE).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% @doc Starts the server.
%% @spec start_link() -> {ok, pid()} | {error, Reason}
%% @end
%%--------------------------------------------------------------------
start_link() ->
	gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
	process_flag(trap_exit, true),
	{ok, #state{}}.

alloc() ->
	gen_server:call(?MODULE, alloc, drproxy_client:config(response_timeout) + 1000).

monitor(Pid, N) ->
	gen_server:cast(?MODULE, {monitor, Pid, N}).

monitor(N) ->
	gen_server:cast(?MODULE, {monitor, N}).

alive(Resource, N) ->
	gen_server:cast(?MODULE, {alive, Resource, N}).

check() ->
	gen_server:cast(?MODULE, check).

status() ->
	gen_server:cast(?MODULE, status).

release(Pid) ->
	gen_server:cast(?MODULE, {release, Pid}).

restart(N) ->
	gen_server:cast(?MODULE, {restart, N}).

restart_all() ->
	gen_server:cast(?MODULE, restart_all).

%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(alloc, {From, _}, State) ->
	case State#state.resources of
		[] ->
			{reply, not_allocated, State#state{alert = true, alert_pid = From}};
		Resources ->
			case utils:delete_all(nil, lists:map(
				fun({Resource, N, R}) ->
					case R > drproxy_client:config(concurrency) of true -> nil; false -> {Resource, N, R} end
				end,
			Resources)) of
				[] ->
					{reply, not_allocated, State#state{alert = true, alert_pid = From}};
				ChosenResources ->
					I = random:uniform(length(ChosenResources)),
					{ResourcePid, N, R} = lists:nth(I, ChosenResources),
					dpc_server:dequeue(),
					{reply, {allocated, ResourcePid}, State#state{
						resources = [{ResourcePid, N, R + 1} | lists:delete({ResourcePid, N, R}, Resources)],
						alert = false
					}}
			end
	end;

handle_call(Msg, _From, State) ->
	error_logger:error_msg("Invalid call (resource_server): ~w~n", [Msg]),
	{noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({monitor, Pid, N}, State) ->
	link(Pid),
	case lists:keysearch(Pid, 1, State#state.resources) of
		{value, {Pid, N, R}} ->
			CurrentResources = lists:delete({Pid, N, R}, State#state.resources),
			CurrentSpares = State#state.spares,
			
			FewerResourcesThanNecessary = drproxy_client:config(client_resources) - drproxy_client:config(client_spares) - length(CurrentResources),
			NumberOfSparesLeft = length(CurrentSpares),
			NumberOfSparesUsed = drproxy_client:config(client_spares) - NumberOfSparesLeft,
			NumberOfSparesShouldBeUsed = trunc(FewerResourcesThanNecessary / drproxy_client:config(hot_swap_every)),
			NewSpares = NumberOfSparesShouldBeUsed - NumberOfSparesUsed,
						
			case (NewSpares > 0) and (NewSpares =< NumberOfSparesLeft) of
				true ->
					NewResources = lists:map(fun(I) -> lists:nth(I, CurrentSpares) end, lists:seq(1, NewSpares)),
					error_logger:info_msg("Adding new resources to the pool: ~w~n", [NewResources]),
					Resources = CurrentResources ++ NewResources,
					case NewSpares < NumberOfSparesLeft of
						true ->
							Spares = lists:map(fun(I) -> lists:nth(I, CurrentSpares) end, lists:seq(NewSpares + 1, NumberOfSparesLeft));
						false ->
							Spares = []
					end;
				false ->
					{Resources, Spares} = {CurrentResources, CurrentSpares}
			end,
			
			Monitored = [{Pid, N, 0} | State#state.monitored],
			
			{noreply, State#state{
				resources = Resources,
				monitored = Monitored,
				spares = Spares
			}};
		_ ->
			case lists:keysearch(Pid, 1, State#state.spares) of
				{value, {Pid, N, R}} ->
					{noreply, State#state{
						monitored = [{Pid, N, 0} | State#state.monitored],
						spares = lists:delete({Pid, N, R}, State#state.spares)
					}};
				_ ->
					case lists:member({Pid, N, 0}, State#state.monitored) of
						true -> {noreply, State};
						false ->
							{noreply, State#state{
								monitored = [{Pid, N, 0} | State#state.monitored]
							}}
					end
			end
	end;

handle_cast({alive, Resource, N}, State) ->
	case (drproxy_client:config(client_spares) == 0) or
		 (drproxy_client:config(client_resources) - drproxy_client:config(client_spares) - 1 >
			length(State#state.resources) - length(State#state.spares)) of
		% if the # of running resources there should be is bigger than the number of running resources there are, make it a resources
		true ->
			error_logger:info_msg("Resource ~w returned~n", [N]),
			{noreply, State#state{
				resources = [{Resource, N, 0} | State#state.resources],
				monitored = lists:delete({Resource, N, 0}, State#state.monitored)
			}};
		% otherwise make it a spare
		false ->
			error_logger:info_msg("Resource ~w is now a spare~n", [N]),
			{noreply, State#state{
				spares = [{Resource, N, 0} | State#state.spares],
				monitored = lists:delete({Resource, N, 0}, State#state.monitored)
			}}
	end;

handle_cast(check, State) ->
	spawn(resource_server, check, [State#state.resources]),
	{noreply, State};

handle_cast(status, State) ->
	error_logger:info_msg("Resources: ~w~nMonitored: ~w~nSpares: ~w~n", [State#state.resources, State#state.monitored, State#state.spares]),
	{noreply, State};

handle_cast({release, Pid}, State) ->
	case lists:keysearch(Pid, 1, State#state.resources) of
		{value, {Pid, N, 0}} ->
			case State#state.alert and not (State#state.alert_pid == false) of 
				true ->
					State#state.alert_pid ! resource_free;
				false ->
					false
			end,
			{noreply, State#state{alert_pid = false, alert = false}};
		{value, {Pid, N, R}} ->
			case State#state.alert and not (State#state.alert_pid == false) of
				true ->
					State#state.alert_pid ! resource_free;
				false ->
					false
			end,
			{noreply, State#state{
				resources = [{Pid, N, R - 1} | lists:delete({Pid, N, R}, State#state.resources)],
				alert_pid = false,
				alert = false
			}};
		false ->
			{noreply, State}
	end;

handle_cast({monitor, N}, State) ->
	case lists:keysearch(N, 2, State#state.resources) of
		{value, {Resource, N, _R}} ->
			monitor(Resource, N);
		false ->
			case lists:keysearch(N, 2, State#state.spares) of
				{value, {Resource2, N, _R}} ->
					monitor(Resource2, N);
				false ->
					false
			end
	end,
	{noreply, State};

handle_cast({restart, N}, State) ->
	case lists:keysearch(N, 2, State#state.resources) of
		{value, {Resource, N, _R}} ->
			resource:restart(Resource);
		false ->
			case lists:keysearch(N, 2, State#state.monitored) of
				{value, {Resource2, N, _R}} ->
					resource:restart(Resource2);
				false ->
					case lists:keysearch(N, 2, State#state.spares) of
						{value, {Resource3, N, _R}} ->
							resource:restart(Resource3);
						false ->
							false
					end
			end
	end,
	{noreply, State};

handle_cast(restart_all, State) ->
	spawn(resource_server, restart_all, [State#state.resources, State#state.monitored, State#state.spares]),
	{noreply, State};

handle_cast(Msg, State) ->
	error_logger:error_msg("Invalid cast (resource_server): ~w~n", [Msg]),
	{noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({'EXIT', Pid, _Reason}, State) -> 
	case lists:keysearch(Pid, 1, State#state.resources) of
		{value, {Pid, N, R}} ->
			{noreply, State#state{
				resources = lists:delete({Pid, N, R}, State#state.resources)
			}};
		false ->
			case lists:keysearch(Pid, 1, State#state.monitored) of
				{value, {Pid, N2, R2}} ->
					{noreply, State#state{
						monitored = lists:delete({Pid, N2, R2}, State#state.monitored)
					}};
				false ->
					case lists:keysearch(Pid, 1, State#state.spares) of
						{value, {Pid, N3, R3}} ->
							{noreply, State#state{
								spares = lists:delete({Pid, N3, R3}, State#state.spares)
							}};
						false ->
							{noreply, State}
					end
			end
	end;
handle_info(Info, State) ->
	error_logger:error_msg("Invalid info (resource_server): ~w~n", [Info]),
	{noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
	ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) -> {ok, State}.
	
%%====================================================================
%%% Internal functions
%%====================================================================

restart_all(Resources, Monitored, Spares) ->
	restart_all(Spares ++ Monitored, now),
	case length(Spares) > 0 of true ->
		receive after drproxy_client:config(startup_delay) -> restart_all(Resources, length(Spares)) end;
	false ->
		restart_all(Resources, 0)
	end.

restart_all([], _NumSpares) ->
	true;
restart_all(Resources, 0) ->
	lists:foreach(fun ({Resource, _N, _R}) ->
		resource:restart(Resource),
		receive after round(drproxy_client:config(startup_delay) /
			(case length(Resources) > 3 of true -> length(Resources) - 2; false -> 1 end)
		) -> true end
	end, Resources);
restart_all(Resources, now) ->
	lists:foreach(fun ({Resource, _N, _R}) ->
		resource:restart(Resource)
	end, Resources);
restart_all(Resources, NumSpares) ->
	HowMany = case length(Resources) < NumSpares of
		true ->
			1 - trunc(length(Resources) / NumSpares);
		false ->
			0
	end,
	
	{NewResources, RestResources} = lists:split(length(Resources) + HowMany, Resources),
	restart_all(NewResources, now),
	receive after drproxy_client:config(startup_delay) -> 
		restart_all(RestResources, NumSpares)
	end.

check(Resources) ->
	lists:foreach(fun ({Resource, _N, _R}) -> resource:check(Resource) end, Resources).